#include <algorithm/analytical_model/algorithm/cuda/Utils.h>
#include <algorithm/analytical_model/io/csv/WriteFluxMap.h>
#include <utils/Format.h>

#include <fstream>
#include <memory>
#include <string>
#include <string_view>

#include <boost/algorithm/string.hpp>

#include <oneapi/tbb.h>

#undef min
#undef max

using namespace solar;

namespace solar::io::csv::cpu::internal
{
    constexpr std::string_view kFormatStrings[] = {"{0:#1G}", "{0:#2G}", "{0:#3G}", "{0:#4G}",
                                                   "{0:#5G}", "{0:#6G}", "{0:#7G}", "{0:#8G}",
                                                   "{0:#9G}", "{0:#10G}"};

    template <std::size_t N>
        requires(N <= 0)
    auto getFormatString(std::size_t /*precision*/, float /*num*/) -> std::string
    {
        return {};
    }

    template <std::size_t N> auto getFormatString(std::size_t precision, float num) -> std::string
    {
        if (precision == N)
        {
            return solar::format(kFormatStrings[N - 1], num);
        }
        return getFormatString<N - 1>(precision, num);
    }
} // namespace solar::io::csv::cpu::internal

using namespace solar::io::csv::cpu;

void solar::io::csv::writeFluxMap(const std::string_view& file_name, float* d_data, int height,
                                  int width, int precision, bool high_accuracy)
{
    if (high_accuracy)
    {
        return solar::io::csv::cpu::writeFluxMap(file_name, d_data, height, width, precision);
    }
    return solar::io::csv::cuda::writeFluxMap(file_name, d_data, height, width, precision);
}

void solar::io::csv::cpu::writeFluxMap(const std::string_view& file_name, float* d_data, int height,
                                       int width, int precision)
{
    auto data = std::make_shared<float[]>(height * width);
    auto* data_ptr = data.get();
    solar::cuda::gpu2cpu(data_ptr, d_data, height * width);
    auto result = oneapi::tbb::parallel_reduce(
        oneapi::tbb::blocked_range<std::uint64_t>(0, height * width), std::string{},
        [&](const oneapi::tbb::blocked_range<std::uint64_t>& range, std::string partial_sum)
        {
            for (std::uint64_t i = range.begin(); i < range.end(); i++)
            {
                auto num_str = internal::getFormatString<std::size(internal::kFormatStrings)>(
                    precision, data[i]);
                if (boost::contains(num_str, "E-"))
                {
                    partial_sum.append(
                        internal::getFormatString<std::size(internal::kFormatStrings)>(precision,
                                                                                       0.0));
                }
                else
                {
                    partial_sum.append(num_str.substr(
                        0, std::min(static_cast<std::uint64_t>(precision + 1), num_str.size())));
                }
                if ((i + 1) % width == 0)
                {
                    partial_sum.append("\n");
                }
                else
                {
                    partial_sum.append(",");
                }
            }
            return partial_sum;
        },
        std::plus<>());
    std::ofstream fout(file_name.data());
    fout.write(result.c_str(), result.size());
    fout.flush();
    fout.close();
}
